package buco.integrity.assertion;

import java.util.ArrayList;
import java.util.List;

import buco.core.api.Bundle;
import buco.core.api.BundleWire;
import buco.core.api.ExportPackageCapability;
import buco.core.api.IntegrityCheck;
import buco.core.api.IntegrityReport;
import buco.core.api.Version;

public class AssertionIntegrityCheck extends IntegrityCheck {	
	@Override
	public void checkIntegrity(Bundle bundle, IntegrityReport report) {
		List<AssertBundleConnected> assertBundleList = bundle.getSemanticConstraints("assertBundleConnected", AssertBundleConnected.class);
		
		for(AssertBundleConnected abc : assertBundleList) {
			checkBundleConnected(abc, bundle, report); 
		}
		
		List<AssertPackageConnected> assertPackageList = bundle.getSemanticConstraints("assertPackageConnected", AssertPackageConnected.class);
		if(assertPackageList.size() > 0) { 
			ArrayList<ExportPackageCapability> exports = getExportedPackages(bundle);
			
			for(AssertPackageConnected apc : assertPackageList) {
				checkPackageConnected(apc, exports, report); 
			}
		}
	}
	
	private void checkBundleConnected(AssertBundleConnected assertion, Bundle bundle, IntegrityReport report) { 
		List<BundleWire> wires = composition.getOutgoingWires(bundle, true);
		boolean bundleFound = false; 
		
		for(BundleWire wire : wires) {
			Bundle to = wire.getTo(); 
			if(to.getBundleSymbolicName().equals(assertion.getBundleSymbolicName())) {
				bundleFound = true; 
				if(assertion.getVersion() != null) {
					if(to.getBundleVersion() == null || (!to.getBundleVersion().toString().equals(assertion.getVersion().toString()))) {
						reportVersionMissmatch(to.getBundleVersion(), assertion.getVersion(), report); 
					}
				}
			}
		}
		
		if(!bundleFound) {
			report.error(String.format("assertBundleConnected failed. Bundle %s is not connected.", assertion.getBundleSymbolicName())); 
		}
	}
	
	/**
	 * Gets all packages in the composition, that can be imported by the given bundle.
	 * @param bundle
	 * @return
	 */
	private ArrayList<ExportPackageCapability> getExportedPackages(Bundle bundle) {
		ArrayList<ExportPackageCapability> exports = new ArrayList<ExportPackageCapability>();		 
		GetExportedPackagesVisitor visitor = new GetExportedPackagesVisitor();
		List<BundleWire> wires = composition.getOutgoingWires(bundle, true);

		// Get all exported packages
		for(BundleWire wire : wires) {
			Bundle to = wire.getTo(); 
			to.accept(visitor); 
			exports.addAll(visitor.getExports()); 
		}
		
		return exports; 
	}
	
	private void checkPackageConnected(AssertPackageConnected assertion, ArrayList<ExportPackageCapability> exports, IntegrityReport report) {						
		boolean packageFound = false; 		
		for(ExportPackageCapability epc : exports) {
			if(epc.getPackageName().equals(assertion.getPackageName())) {
				packageFound = true; 
				if(assertion.getVersion() != null) {
					if(epc.getPackageVersion() == null || (!epc.getPackageVersion().toString().equals(assertion.getVersion().toString()))) {
						reportVersionMissmatch(epc.getPackageVersion(), assertion.getVersion(), report); 
					}
				}
			}
		}
		
		if(!packageFound) { 
			report.error(String.format("assertPackageConnected failed. Package %s is not connected.", assertion.getPackageName()));
		}
		
	}
	
	private void reportVersionMissmatch(Version is, Version expected, IntegrityReport report) {
		String strIs = is == null ? "null" : is.toString(); 
		String strExp = expected == null ? "null" : expected.toString(); 
		
		report.error(String.format("Assertion failed: Bundle version is: %s; expected: %s", strIs, strExp)); 
	}
	


}
